package dal;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonIOException;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;

import dal.datastore.Database;
import dal.datastore.PhysicalDataManipulation;
import dal.datastore.TableData;
import dto.ColumnDTO;
import dto.RowDTO;

/**
 * Database manipulations in here.
 * 
 * @author Stanley
 * 
 */
public class JMLDatabase {
	private static Socket server = null;
	private DataOutputStream dos;
	private DataInputStream dis;

	private PhysicalDataManipulation pdm;
	private Database db;

	public JMLDatabase() throws UnknownHostException, IOException {
		super();
		if (JMLDatabase.server != null) {
			this.dos = new DataOutputStream(
					JMLDatabase.server.getOutputStream());
			this.dis = new DataInputStream(JMLDatabase.server.getInputStream());
		}
	}

	/**
	 * get data and all things about an existing database
	 * 
	 * @param dbName
	 *            string
	 * @return null
	 */
	public JMLDatabase(String dbName) {
		super();
		pdm = new PhysicalDataManipulation();
		this.db = new Database(dbName);
	}

	public JMLDatabase(String ip, int port) {
		super();
		try {
			JMLDatabase.server = new Socket(ip, port);
			this.dos = new DataOutputStream(
					JMLDatabase.server.getOutputStream());
			this.dos.flush();
			this.dis = new DataInputStream(JMLDatabase.server.getInputStream());
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * connect to server
	 * 
	 * @param ip
	 *            string
	 * @param port
	 *            integer
	 * @throws IOException
	 * @throws UnknownHostException
	 */
	public void connectToServer(String ip, int port)
			throws UnknownHostException, IOException {
		JMLDatabase.server = new Socket(ip, port);
		this.dos = new DataOutputStream(JMLDatabase.server.getOutputStream());
		this.dis = new DataInputStream(JMLDatabase.server.getInputStream());
	}

	/**
	 * Login with user name and password
	 * 
	 * @param username
	 * @param password
	 * @return true if login successful
	 * @throws Exception
	 */
	public boolean login(String username, String password) throws Exception {
		try {
			this.dos.writeInt(EMessageCode.login);
			this.dos.writeUTF(username);
			this.dos.writeUTF(password);
			this.dos.flush();
			boolean isLogin = this.dis.readBoolean();
			if (!isLogin) {
				throw new Exception("Wrong username and password");
			}
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * Logout
	 * 
	 * @throws Exception
	 */
	public void logout() throws Exception {
		try {
			if (JMLDatabase.server.isClosed())
				return;
			this.dos.writeInt(EMessageCode.logout);
			this.dos.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Create database with name
	 * 
	 * @param dbname
	 * @return
	 * @throws Exception
	 */
	public boolean createDatabse(String dbname) throws Exception {
		if (!dbname.isEmpty()) {
			this.dos.writeInt(EMessageCode.createDatabase);
			this.dos.writeUTF(dbname);
			this.dos.flush();
			boolean res = this.dis.readBoolean();
			return res;
		} else {
			throw new Exception("Database name is invalid");
		}
	}

	/**
	 * Create table with table name
	 * 
	 * @param dbname
	 * @param tableName
	 * @return true if create succeed.
	 * @throws Exception
	 */
	public boolean createTable(String dbname, String tableName)
			throws Exception {
		if (dbname.isEmpty() || tableName.isEmpty()) {
			throw new Exception("Database name or table name is null");
		}
		return true;
	}

	/**
	 * Get all database name.
	 * 
	 * @return ArrayList
	 * @throws IOException
	 * @throws Exception
	 */
	public ArrayList<String> getAllDatabaseName() throws IOException {
		this.dos.writeInt(EMessageCode.getAllDatabaseName);
		this.dos.flush();
		String json = this.dis.readUTF();
		Gson gson = new Gson();
		JsonParser parser = new JsonParser();
		JsonArray array = parser.parse(json).getAsJsonArray();
		ArrayList<String> res = new ArrayList<>();
		for (int i = 0; i < array.size(); i++) {
			String dbName = gson.fromJson(array.get(i), String.class);
			res.add(dbName);
		}
		return res;
	}

	/**
	 * Drop a database
	 * 
	 * @param dbname
	 * @return true if drop successful and vice versa.
	 * @throws Exception
	 */
	public boolean dropDatabase(String dbname) throws Exception {
		if (!dbname.isEmpty()) {
			this.dos.writeInt(EMessageCode.dropDatabase);
			this.dos.writeUTF(dbname);
			this.dos.flush();
			boolean res = this.dis.readBoolean();
			return res;
		} else {
			throw new Exception("Database name is null");
		}
	}

	/**
	 * @param dbname
	 *            database name which is contains table.
	 * @param tableName
	 *            table name
	 * @param columns
	 *            a list of columns.
	 * @return true if create successful and vice versa.
	 * @throws Exception
	 */
	public boolean createTable(String dbname, String tableName,
			ArrayList<ColumnDTO> columns) throws Exception {
		if (dbname != null && tableName != null) {
			this.dos.writeInt(EMessageCode.createTable);
			this.dos.writeUTF(dbname);
			this.dos.writeUTF(tableName);
			this.dos.flush();
			Gson gson = new Gson();
			String json = gson.toJson(columns);
			this.dos.writeUTF(json);
			this.dos.flush();
			boolean res = this.dis.readBoolean();
			return res;
		} else {
			throw new Exception("Table name or database name is invalid");
		}
	}

	/**
	 * Get all table name from database.
	 * 
	 * @param _databaseName
	 * @return ArrayList<String>
	 * @throws Exception
	 */
	public ArrayList<String> getAllTableName(String dbname) throws Exception {
		if (!dbname.isEmpty()) {
			this.dos.writeInt(EMessageCode.getAllTableName);
			this.dos.writeUTF(dbname);
			this.dos.flush();
			String json = this.dis.readUTF();

			Gson gson = new Gson();
			JsonParser parser = new JsonParser();

			System.out.println(json);
			JsonArray array = parser.parse(json).getAsJsonArray();
			ArrayList<String> res = new ArrayList<>();
			for (int i = 0; i < array.size(); i++) {
				String tblName = gson.fromJson(array.get(i), String.class);
				res.add(tblName);
			}
			return res;
		} else
			throw new Exception("Database name is null");
	}

	/**
	 * Drop a table with dbname and table name
	 * 
	 * @param dbname
	 * @param tableName
	 * @return true if successful and vice versa.
	 * @throws Exception
	 */
	public boolean dropTable(String dbname, String tableName) throws Exception {
		if (dbname == null || tableName == null) {
			throw new Exception("Database name or tablename is null");
		}
		this.dos.writeInt(EMessageCode.dropTable);
		this.dos.writeUTF(dbname);
		this.dos.writeUTF(tableName);
		this.dos.flush();
		boolean res = this.dis.readBoolean();
		return res;
	}

	/**
	 * insert a row into table
	 * 
	 * @param tableName
	 *            string
	 * @param rowData
	 *            rowDTO
	 * @return boolean true if successful
	 * @author JML
	 * @throws Exception
	 */
	public boolean insertRow(String dbName, String tableName, RowDTO rowData)
			throws Exception {
		if (dbName == null || tableName == null) {
			throw new Exception("Database name or tablename is null");
		}
		this.dos.writeInt(EMessageCode.insertRow);
		this.dos.writeUTF(dbName);
		this.dos.writeUTF(tableName);
		this.dos.flush();
		Gson gson = new Gson();
		String rowJson = gson.toJson(rowData);
		this.dos.writeUTF(rowJson);
		this.dos.flush();
		boolean res = this.dis.readBoolean();
		return res;
	}

	/**
	 * Insert rows to table
	 * 
	 * @param dbName
	 * @param tableName
	 * @param rows
	 * @return
	 * @throws Exception
	 * @author Stanley, hathao298
	 */
	public boolean insertRows(String dbName, String tableName,
			ArrayList<RowDTO> rows) throws Exception {
		if (dbName == null || tableName == null) {
			throw new Exception("Database name or tablename is null");
		}
		this.dos.writeInt(EMessageCode.insertRows);
		this.dos.writeUTF(dbName);
		this.dos.writeUTF(tableName);
		this.dos.flush();
		Gson gson = new Gson();
		String rowJson = gson.toJson(rows);
		this.dos.writeUTF(rowJson);
		this.dos.flush();
		boolean res = this.dis.readBoolean();
		return res;
	}

	/**
	 * Replace table data in database data if it's exists.
	 * 
	 * @param dbName
	 * @param tblName
	 * @param tblData
	 * @return
	 * @throws JsonIOException
	 * @throws JsonSyntaxException
	 * @throws IOException
	 */
	public boolean replaceTableData(String dbName, String tblName,
			TableData tblData) throws IOException {
		this.dos.writeInt(EMessageCode.replaceTableData);
		this.dos.writeUTF(dbName);
		this.dos.writeUTF(tblName);
		Gson gson = new Gson();
		String json = gson.toJson(tblData);
		this.dos.writeUTF(json);
		this.dos.flush();
		boolean res = this.dis.readBoolean();
		return res;
	}

	/**
	 * Select 200 rows from table data
	 * 
	 * @param tableName
	 *            string
	 * @return arrayList<Row>
	 * @author JML, Stanley
	 * @throws IOException
	 * @throws FileNotFoundException
	 * @throws JsonSyntaxException
	 * @throws JsonIOException
	 */
	public ArrayList<RowDTO> select200Rows(String dbName, String tableName)
			throws IOException {
		this.dos.writeInt(EMessageCode.select200Rows);
		this.dos.writeUTF(dbName);
		this.dos.writeUTF(tableName);
		String json = this.dis.readUTF();
		if(json.equals("null")){
			return null;
		}
		Gson gson = new Gson();
		JsonParser parser = new JsonParser();
		JsonArray array = parser.parse(json).getAsJsonArray();		
		ArrayList<RowDTO> res = new ArrayList<RowDTO>();
		for (int i = 0; i < array.size(); i++) {
			RowDTO row = gson.fromJson(array.get(i), RowDTO.class);
			res.add(row);
		}
		return res;
	}

	/**
	 * Get all columns of table
	 * 
	 * @param dbname
	 * @param tableName
	 * @return ArrayList<ColumnDTO>
	 * @throws Exception
	 */
	public ArrayList<ColumnDTO> getTableColumns(String dbname, String tableName)
			throws Exception {
		if (dbname != null && tableName != null) {
			this.dos.writeInt(EMessageCode.getTableColumns);
			this.dos.writeUTF(dbname);
			this.dos.writeUTF(tableName);
			this.dos.flush();
			String json = this.dis.readUTF();
			Gson gson = new Gson();
			JsonParser parser = new JsonParser();
			JsonArray array = parser.parse(json).getAsJsonArray();
			ArrayList<ColumnDTO> res = new ArrayList<>();
			for (int i = 0; i < array.size(); i++) {
				ColumnDTO colName = gson
						.fromJson(array.get(i), ColumnDTO.class);
				res.add(colName);
			}
			return res;
		} else {
			throw new Exception("Table name or database name is invalid");
		}
	}
	
}
